package org.restorator.di;

import org.restorator.utill.GeneralUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static com.google.common.base.Preconditions.checkNotNull;

/**
* @author Snisar Bogdan.
*/
class ServiceFactory {

    public ServiceFactory(ServiceBuilder builder) {
        buildTrees.putAll(builder.getContainer()); //todo
    }

    private static Logger log = LoggerFactory.getLogger(GeneralUtil.getClassName());

    private ConcurrentHashMap<String, Object> store = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, Ref> buildTrees = new ConcurrentHashMap<>();

    /**
     * Get object from cache or build it.
     *
     * @param key object id key
     * @return object
     */
    public Object get(String key) throws IllegalAccessException, InvocationTargetException, InstantiationException {
        log.trace("Create reference by key {}", key);
        if(store.containsKey(key)) {
            log.trace("Cached instance, return from store");
            return store.get(key);
        } else {
            return create(buildTrees.get(key));

        }
    }

    /**
     *
     * @param ref
     * @return create
     */
    private Object create(Ref ref) throws IllegalAccessException, InstantiationException, InvocationTargetException {
            // if factory and construct null
            AccessibleObject creator = (ref.constructor != null) ? ref.constructor : ref.factory;
            Object[] args = (ref.constructor != null) ? ref.constructorArgs : ref.factoryArgs;
            return createInstance(ref, creator, args);
    }

    private Object createInstance(Ref ref, AccessibleObject creator, Object[] args)
            throws IllegalAccessException, InvocationTargetException, InstantiationException {
        Object returnResult;

//        log.trace();
        if(creator == null) {
            returnResult = ref.clazz.newInstance();
        } else {
            Object[] arguments = new Object[args.length];
            for (int j = 0; j < ref.constructorArgs.length; j++) {
                Object arg = args[j];
                arguments[j] = (arg instanceof Ref)
                        ? create((Ref) arg) // recursive call for create()!!
                        : arg;
            }
            if(creator instanceof Constructor<?>) {
                 returnResult = ((Constructor<?>) creator).newInstance(arguments);
            } else if(creator instanceof Method) {
                 returnResult = ((Method) creator).invoke(null, arguments); // method must be static
            } else {
                 throw new IllegalArgumentException("illegal creator object");
            }
        }
        return tryCache(ref, returnResult);
    }


    private Object tryCache(Ref ref, Object o) {
        log.trace("Caching skope is {}", ref.single);
        if(ref.single) {
            store.put(ref.key, o);
        }
        return o;
    }

}
